/*
 * KeyboardHero
 * Version: 0.1
 * Date: 2009-04-01
 * Author: gp08-nhn
 */

/*---------------------------------------------------------------------------*\
 |                                                                           |
 |  KeyboardHero - a MIDI Keyboard learning / playing software               |
 |  Copyright (c) 2008-2009 gp08-nhn (see below)                             |
 |                                                                           |
 |  ////////////////////////////////////////////////////                     |
 |  //                                                //                     |
 |  //  Group ID   : gp08-nhn                         //                     |
 |  //  Supervisor : Henrik Nilsson (nhn)             //                     |
 |  //                                                //                     |
 |  //  Members :                                     //                     |
 |  //  sxa37u - 4058466 - Sarper Aydogan             //                     |
 |  //  dnk07u - 4071909 - Daniel Nicholas Kiss       //                     |
 |  //  rdo07u - 4074918 - Ricardo De Sousa Oliveira  //                     |
 |  //  jxs18u - 4084162 - Jia Shi                    //                     |
 |  //  oxs18u - 2101743 - Oana-Roxana Stan           //                     |
 |  //  zxy18u - 4084134 - Zuonan Yang                //                     |
 |  //                                                //                     |
 |  ////////////////////////////////////////////////////                     |
 |                                                                           |
 |  All rights reserved.                                                     |
 |                                                                           |  
 |  This Agreement shall be construed under the laws of the United Kingdom,  |
 |  excluding rules regarding conflicts of law. This license is written in   |
 |  English, and English is its controlling language.                        |
 |                                                                           |
 |  Redistribution and use in binary form, without modification, are         |
 |  permitted provided that the following conditions are met:                |
 |                                                                           |
 |  Redistributions must reproduce the above copyright notice, this list     |
 |  of conditions and the following disclaimer in the documentation          |
 |  and/or other materials provided with the distribution.                   |
 |  Neither the name of the author nor the names of its contributors may     |
 |  be used to endorse or promote products derived from this software        |
 |  without specific prior written permission.                               |
 |                                                                           |
 |  Redistribution of modified binaries or modified setup packages           |
 |  allowed only with prior written permission of the author.                |
 |                                                                           |
 |  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS      |
 |  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT        |
 |  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR    |
 |  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR    |
 |  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,    |
 |  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,      |
 |  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR       |
 |  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY      |
 |  OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT             |
 |  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE    |
 |  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.     |
 |                                                                           |
 |  Note that a separate less restrictive license applies to open source     |
 |  parts, downloadable separately:                                          |
 |                                                                           |
 |  JOGL - Java OpenGL - an OpenGL wrapper library for Java                  |
 |  Copyright (c) 2003-2007 Sun Microsystems, Inc. All Rights Reserved.      |
 |  /Full license(s) included separetley./                                   |
 |                                                                           |
 |  SuperBonBon Industries UPNPLib - a library for UPNP (TM) under JRE       |
 |  Copyright (c) 2002 The Apache Software Foundation. All rights reserved.  |
 |  /Full license(s) included separetley./                                   |
 |                                                                           |
\*---------------------------------------------------------------------------*/

package keyboardhero;

/* 
 * dnk07u's TODO-plans:
 * + midi meta properties: http://www.sonicspot.com/guide/midifiles.html
 * + midi & game synchronization - see below
 * + info display + better notes: with border, letter inside; disappear under keyboard, ?rounded rectangles?
 * + messages
 * + new random song
 * + factor
 * + background
 * + songNames in toplists (~30)
 * + online testing (~60)
 * + icons
 * + individual report (~180)
 * + final group report
 * + midifiles
 * × revise individual report
 * + language / rules
 * + go through todos
 * + backgrounds
 * + submit package together
 * + main lab, everything together
 * 
 * × song database: local
 * × upnp local connection detection + port forwarding
 * × javadoc
 * × debugging (a lot)
 * --------------------- TILL SUBMIT DEADLINE ---------------------------
 * + performance
 * + song database: local
 * + upnp: port forwarding
 * + multicast: local connection detection
 * + OpenGL about box
 * + open from file
 * + import
 * + webpage style, webpage languages, song database: online
 * + displaying images for open day
 * × import themes
 * × chat
 * × themes
 * × graphical debugging interface
 * - print documents
 * - hungarian language
 * - languages
 * - proper web song list
 * --------------------- TILL OPENDAY ---------------------------
 * - presentation
 * - video -> need music or something
 * --------------------- TILL PRESENTATION ---------------------------
 * - multiplayer interface: create game, join game
 *     ¤ file download
 * - vertical musical note appearance
 * --------------------- FUTURE DEVELOPEMNTS ---------------------------
 * 
 * + key setting, conversions; done by rdo07u -> modified to switch method instead
 * + put the game together
 * + loop synchronization + constant tempo for PPQ
 * + comparing + device handling
 * + settings:
 * 	   + pause key
 *     + first and last keys
 *     + letters on keyboard
 *     + letters on notes
 *     + midi devices
 */

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.util.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.filechooser.FileNameExtensionFilter;

import java.io.*;
import java.net.InetSocketAddress;

import keyboardhero.DialogSongList.*;
import keyboardhero.Util.*;

/**
 * This is the main class of the whole application, it contains the main window to be displayed,
 * which is either the {@link #normalFrame} or the fullscreened {@link #fullFrame}}. It initializes
 * the game at the start of the application and closes it when it is requested by the user.
 * 
 * @author gp08-nhn
 */
public final class KeyboardHero extends JFrame {
	
	//-------------------- VARIABLES --------------------
	
	// ××××× SETTINGS VARIABLES ×××××
	
	/**
	 * The version number of the application.
	 * 
	 * @my.val {@value}
	 */
	static final String APP_VERSION = "0.3";
	/**
	 * A unique integer representing the version of the application. It is used for update checking
	 * purposes.
	 */
	static final int UPDATE_NUMBER = 3;
	/**
	 * The name of the application.
	 * 
	 * @my.val {@value}
	 */
	static final String APP_NAME = "KeyboardHero";
	/**
	 * The java archive filename of the application.
	 * 
	 * @my.val {@value}
	 */
	static final String APP_FILENAME = APP_NAME + ".jar";
	/**
	 * The title of the application; generation based on {@link KeyboardHero#APP_NAME} and
	 * {@link KeyboardHero#APP_VERSION}.
	 * 
	 * @my.val {@value}
	 */
	static final String APP_TITLE = APP_NAME + " v" + APP_VERSION;
	/**
	 * The author of the application.
	 * 
	 * @my.val {@value}
	 */
	static final String APP_AUTHORS = "gp08-nhn";
	/**
	 * The list of the translators of the language files.
	 */
	static final TreeSet<String> APP_TRANSLATORS = new TreeSet<String>();
	
	static final String APP_LICENSE = "KeyboardHero - a MIDI Keyboard learning / playing software\nCopyright (C) 2008-2009 gp08-nhn (see below)\n\n////////////////////////////////////////////////////\n//                                                //\n//  Group ID   : gp08-nhn                         //\n//  Supervisor : Henrik Nilsson (nhn)             //\n//                                                //\n//  Members :                                     //\n//  sxa37u - 4058466 - Sarper Aydogan             //\n//  dnk07u - 4071909 - Daniel Nicholas Kiss       //\n//  rdo07u - 4074918 - Ricardo De Sousa Oliveira  //\n//  jxs18u - 4084162 - Jia Shi                    //\n//  oxs18u - 2101743 - Oana-Roxana Stan           //\n//  zxy18u - 4084134 - Zuonan Yang                //\n//                                                //\n////////////////////////////////////////////////////\n\nAll rights reserved.\n\nThis Agreement shall be construed under the laws of the United Kingdom, excluding rules regarding conflicts of law. This license is written in English, and English is its controlling language.\n\nRedistribution and use in binary form, without modification, are permitted provided that the following conditions are met:\n\nRedistributions must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.\n\nRedistribution of modified binaries or modified setup packages allowed only with prior written permission of the author.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nNote that a separate less restrictive license applies to open source parts, downloadable separately:\n\nJOGL - Java OpenGL - an OpenGL wrapper library for Java\nCopyright (c) 2003-2007 Sun Microsystems, Inc. All Rights Reserved.\n/Full license(s) included separetley./\n\nSuperBonBon Industries UPNPLib - a library for UPNP (TM) under JRE\nCopyright (c) 2002 The Apache Software Foundation. All rights reserved.\n/Full license(s) included separetley./\n";
	
	/** The data of the icon of the game; it is generated from an image file. */
	private static final byte[] ICONDATA = {71, 73, 70, 56, 57, 97, 32, 0, 32, 0, -9, 0, 0, -100, 8, 0, -104, 8, 16, -96, 12, 0, -83, 0, 0, -108, 24, 0, -108, 33, 0, -100, 32, 0, -105, 33, 41, -83, 16, 24, -75, 0, 0, -75, 8, 0, -75, 16, 16, -75, 41, 41, -67, 0, 0, -67, 8, 8, -67, 16, 16, -67, 24, 24, -67, 24, 33, -67, 33, 33, -58, 16, 24, -50, 16, 16, -58, 28, 37, -50, 16, 24, -42, 20, 24, -34, 12, 16, -40, 27, 33, -34, 33, 33, -42, 33, 41, -34, 33, 41, -25, 33, 41, -25, 41, 33, -42, 41, 41, -108, 49, 0, -108, 57, 0, -100, 57, 0, -100, 66, 0, -100, 74, 0, -100, 82, 0, -91, 41, 49, -83, 74, 16, -79, 45, 53, -51, 45, 45, -30, 57, 45, -25, 41, 49, -25, 49, 49, -17, 49, 49, -17, 57, 49, -100, 82, 8, -94, 93, 5, -91, 123, 0, -67, 115, 20, -50, -124, 33, -42, 99, 49, -42, 107, 49, -42, 115, 49, -83, -116, 0, -83, -108, 0, -75, -116, 0, -65, -108, 20, -50, -108, 24, -50, -100, 24, -50, -116, 33, -50, -124, 41, 16, 49, -124, 32, 61, 115, 12, 61, -108, 21, 76, -86, 41, 74, -108, 74, 104, -117, 45, 86, -83, 79, 120, -78, -91, 66, 66, -96, 86, 90, -96, 90, 103, -113, -122, 101, 115, -108, -124, 118, -111, -89, -100, -117, 123, -77, 127, -119, 24, 90, -58, 28, 99, -42, 41, 103, -50, 45, 111, -38, 24, 107, -25, 32, 120, -12, 33, -124, -5, 41, -122, -7, 66, 115, -58, 57, 123, -34, 49, -120, -9, 60, -114, -17, 78, -125, -54, 79, -114, -29, 102, -108, -67, 104, -106, -32, -58, 49, 57, -58, 57, 66, -67, 66, 66, -58, 70, 74, -50, 74, 82, -30, 49, 57, -42, 82, 66, -34, 86, 57, -17, 49, 57, -25, 57, 57, -17, 57, 57, -25, 66, 57, -17, 66, 57, -17, 66, 66, -25, 78, 61, -25, 90, 61, -34, 99, 57, -34, 107, 57, -50, 90, 90, -67, 107, 115, -38, 98, 107, -50, 115, 123, -67, -124, -116, -54, -125, -125, -58, -100, -91, -50, -100, -100, -50, -100, -91, -42, -116, -116, -34, -124, -116, -34, -116, -116, -25, -116, -116, -37, -108, -103, -50, -91, 16, -67, -83, 0, -58, -83, 8, -50, -83, 8, -67, -75, 0, -58, -75, 0, -50, -83, 16, -54, -71, 4, -50, -67, 8, -50, -58, 0, -42, -58, 0, -50, -50, 0, -42, -50, 0, -50, -42, 0, -42, -42, 0, -34, -42, 0, -34, -34, 0, -25, -34, 0, -34, -34, 8, -25, -25, 0, -17, -25, 0, -25, -25, 8, -17, -17, 0, -9, -17, 0, -1, -17, 0, -17, -17, 8, -9, -17, 8, -9, -9, 0, -1, -9, 0, -1, -9, 8, -1, -1, 0, -1, -1, 8, -58, -91, 24, -50, -75, 24, -67, -75, 49, -50, -67, 16, -58, -58, 49, -42, -50, 41, -34, -42, 16, -13, -21, 32, -9, -17, 24, -17, -17, 33, -9, -17, 33, -17, -17, 41, -9, -9, 16, -1, -9, 16, -9, -9, 24, -1, -9, 24, -42, -42, 57, -83, -75, 66, -50, -42, 66, -83, -83, 74, -83, -75, 74, -75, -75, 74, -50, -50, 74, -61, -50, 101, -25, -25, 66, -17, -17, 49, -9, -17, 49, -9, -9, 57, -42, -34, 82, -42, -34, 99, -42, -25, 107, -23, -20, 93, -108, -83, 115, -91, -83, 115, -67, -58, 115, -108, -91, -124, -100, -83, -124, -75, -67, -124, -67, -50, -124, -116, -91, -108, -83, -50, -100, -83, -58, -91, -75, -58, -91, -75, -42, -91, -91, -58, -83, -83, -58, -83, -58, -91, -83, 66, -100, -9, 82, -91, -9, 90, -83, -17, 90, -83, -1, 90, -58, -1, 115, -91, -58, 123, -91, -75, 123, -83, -34, -100, -67, -75, -67, -83, -75, -124, -83, -58, -83, -58, -58, -124, -83, -25, 123, -75, -17, -124, -50, -9, -124, -50, -1, -104, -46, -13, -58, -42, -124, -50, -83, -83, -50, -42, 115, -50, -34, 123, -42, -83, -75, -42, -75, -67, -42, -58, -50, -42, -34, 123, -42, -25, 115, -42, -25, -116, -34, -83, -83, -34, -67, -67, -34, -58, -50, -34, -50, -42, -34, -42, -34, -34, -25, -116, -34, -17, -124, -25, -91, -83, -25, -58, -58, -25, -50, -50, -25, -42, -42, -25, -25, -25, -17, -42, -42, -17, -34, -34, -17, -25, -25, -17, -17, -17, -9, -17, -17, -9, -17, -9, -9, -9, -9, -1, -17, -17, -1, -9, -9, -1, -1, -1, -1, -1, -1, 33, -7, 4, 1, 0, 0, -1, 0, 44, 0, 0, 0, 0, 32, 0, 32, 0, 0, 8, -2, 0, -1, 9, 28, -104, -124, 79, -97, 61, 124, 18, -22, 89, -56, 112, 97, -99, -123, 78, -54, 13, -100, 56, -111, -36, 7, 8, 8, 48, 46, -64, 8, 97, 35, -126, -115, 27, 33, 64, -40, -96, -121, 34, -59, 117, 95, 20, 20, 40, -63, -110, -27, -117, -105, 35, 74, -112, -104, 73, 98, 4, 8, 0, 25, -22, -32, 51, 41, -112, 14, 5, 2, 49, 20, 45, 18, -102, -88, 86, 50, 82, -120, 22, 33, 74, -124, 8, -47, -95, 65, 33, 22, 124, 113, -62, -77, -114, 5, 1, 48, 28, 97, -54, -60, -107, 83, -84, 103, -38, -36, -67, 19, 23, -50, 28, 46, 76, -107, 0, -123, 112, 0, -122, 30, 69, 123, 99, 28, -120, 48, 4, -119, -45, -91, 76, -105, 56, -35, -30, 2, -115, -101, -73, 109, 94, -68, 32, -13, -107, -55, 81, 14, 0, 19, -28, -72, 29, -24, -121, -126, -127, 28, -112, -72, -34, -27, -108, -119, 21, 53, 44, 125, -93, 113, -71, -126, 37, 91, -82, 73, -114, 96, 24, -40, 0, 111, 32, 57, 13, 88, 23, 97, -70, -101, -41, 46, 39, 100, 82, -116, 40, 75, -122, 108, 24, -109, 105, -18, 50, 97, 50, 4, 99, 0, -103, 117, -1, -14, -51, 105, 48, 66, -47, 86, 75, -108, 39, 95, -22, -59, -91, 11, -81, 77, -100, 52, -91, -78, 21, -18, -110, -91, 73, -120, -94, -122, 105, 70, -57, 66, -127, 28, -108, 44, -2, -79, -26, 68, 62, 47, -82, 105, 88, -126, 33, -49, -101, -23, -44, 93, 75, -111, 112, 20, -64, 112, 0, 5, -122, 19, -122, 36, -31, -51, -60, 73, 124, -14, 83, -42, 72, -15, 11, 43, -105, 16, -40, 95, 127, -123, -23, 96, 70, 4, -28, -32, 33, -127, 27, -97, 68, 50, -55, -127, 120, -111, 87, -39, 55, 81, 48, 97, -118, 93, -4, -19, 7, -119, 34, 54, -80, -128, 4, 62, -8, 56, -79, -63, 27, -96, 60, -78, 95, 114, -108, 113, -30, -117, 17, 68, -36, -62, -34, 38, 92, 77, -110, 72, 15, 106, -120, 49, -48, 61, 113, -112, -31, -61, 33, -109, -20, -57, -98, 93, -89, 116, 81, 68, 44, 67, 90, 98, -119, 35, 60, -72, -63, -64, 56, 19, -107, 99, 66, 27, 63, 6, -87, 28, 127, -84, 16, 51, 4, 44, -85, -80, 103, 9, 36, -97, -104, -15, 65, 51, 20, -19, 115, -57, 5, 109, 124, -46, -56, -118, 118, -75, -94, -117, 56, 69, 44, 113, 10, 101, -106, 100, 114, -56, 12, 100, 28, -64, -45, 63, 122, 124, -16, -58, 39, -6, -103, 39, 14, 54, -43, 84, 99, -124, 17, -66, 88, 7, 90, 15, 104, -124, -63, -50, -98, -13, 28, 113, -122, 13, -118, 80, 114, -55, 42, -56, 104, 97, 69, 22, 89, 104, 97, -60, 46, -42, 65, 34, 74, 25, 28, -52, -77, -89, 64, -10, -128, 65, 70, 15, -118, -100, 18, 76, 22, -36, 88, 72, -29, 76, 21, 84, 92, 17, -116, 38, -109, 4, 98, -61, 7, 73, -100, 58, -48, 29, 17, 64, 88, -118, 49, 84, 116, 106, 69, 21, 83, 60, 33, 76, 37, -117, -8, -112, -93, 61, -66, 10, 84, 34, 7, 101, -128, 2, 76, 17, 81, 64, 1, -59, 19, 66, 0, 33, 11, -109, 106, 72, 112, 77, -76, 59, 6, -128, -122, 15, -95, 16, 49, 68, 16, 65, -4, 0, -124, 18, -115, 20, 2, -57, 7, 37, -111, -5, 43, 3, 108, -52, 64, 10, 44, -62, 44, 33, 12, 44, -113, 44, 82, 3, 25, 71, -76, 99, -17, 64, -5, -28, -95, -127, 26, 17, -102, -94, -118, 41, -104, 48, -119, -122, 4, -113, 30, 60, 81, -97, 102, 124, 2, -119, 37, -104, 68, -62, 67, 25, 41, -112, 99, 49, 69, -20, 72, 74, 3, 34, -112, 64, 114, -56, 27, 43, -116, 56, 50, 69, -11, 32, -128, 70, 15, -121, 28, -30, 67, -98, 47, -13, -124, -60, 6, 102, -24, -96, 67, 25, 21, -56, -111, -77, 73, -7, -48, -15, 65, 25, 101, 124, -112, -50, -48, 60, -31, 19, 64, 27, 100, -104, -64, -12, -98, 119, -96, 0, -122, -87, 83, -101, -76, 79, 60, -14, 88, 28, 16, 0, 0, 59}; // end initialization
	/** The icon that is actually shown. */
	private static final Image ICON = Util.TOOLKIT.createImage(ICONDATA);
	
	// ××××× CORE VARIABLES ×××××
	/**
	 * A JFrame is a serializable object, thus it requires a unique Serial Version ID.
	 * 
	 * @see java.io.Serializable
	 */
	private static final long serialVersionUID = 3163843967281005845L;
	
	private static TreeMap<String, AbstractDialog> dialogs = new TreeMap<String, AbstractDialog>();
	
	/** The main frame of the application, it contains all the other objects. */
	private static KeyboardHero mainFrame = null, fullFrame = null, normalFrame = null;
	/** The main panel; it contains the drawing canvas. */
	private static JPanel mainPanel = null, subMainPanel = null;
	private static JPanel statusPanel = null;
	private static JLabel status = new JLabel();
	private static JPanel panelNorth, panelSouth, panelWest, panelEast;
	
	private static boolean startup = true;
	private static String loadingString = "Loading...";
	
	// ××××× MENU VARIABLES ×××××
	
	private static JMenuBar menuBar;
	private static JMenu m_Renderer, m_LaF, m_Language, m_Theme;
	private static JMenuItem mPause;
	
	// ××××× SITUATION VARIABLES ×××××
	
	/**
	 * The program runs while this field is true, if this field changes to false, the program will
	 * exit.
	 */
	private static boolean inApp = true;
	
	// ××××× CACHE VARIABLES ×××××
	
	private static boolean fullscreen = Util.getPropBool("fullscreen");
	private static boolean fullscreenChangeAllowed = true;
	/** The lastly generated pass-key. */
	private static String lastPasskey = "";
	
	private static Icon[] statusIcons = new Icon[Status.ICON_TEXTS.length];
	private static Status lastStatus = null;
	private static PriorityQueue<Status> statuses = new PriorityQueue<Status>();
	
	// ××××× ADAPTER VARIABLES ×××××
	
	/**
	 * This is the container adapter added to every container inside the dialog. This method will
	 * add the {@link #KEY_ADAPTER} recursively to every component inside the container. It is used
	 * to close the dialog at the press of an escape, enter or space key.
	 */
	private static final ContainerAdapter CONTAINER_ADAPTER = new ContainerAdapter() {
		public void componentAdded(ContainerEvent e) {
			addListeners(e.getChild());
		} // end method
		/* // unused
		public void componentRemoved(ContainerEvent e) {
		} // end method
		*/
	}; // end class
	
	/**
	 * This is the key adapter added to every component inside the dialog. It is used to close the
	 * dialog at the press of an escape, enter or space key.
	 * 
	 * @see #CONTAINER_ADAPTER
	 */
	private static final KeyAdapter KEY_ADAPTER = new KeyAdapter() {
		@SuppressWarnings("fallthrough")
		public void keyPressed(KeyEvent e) {
			switch(e.getKeyCode()) {
				case KeyEvent.VK_F11:
				case KeyEvent.VK_ESCAPE:
					if(fullscreen) {
						fullScreenMode(false);
					}
					break;
				case KeyEvent.VK_X:
					if(fullscreen && e.isAltDown()) {
						KeyboardHero.exitGame();
					}
					break;
				case KeyEvent.VK_R:
					if(fullscreen && e.isControlDown()) {
						Game.newRandomGame();
					}
					break;
				//				case KeyEvent.VK_P:
				//					if(e.isAltDown() || e.isControlDown()) {
				//						break;
				//					}
				case KeyEvent.VK_PAUSE:
					Game.togglePause();
					break;
			} // end switch
		} // end method
	}; // end class
	
	//-------------------- CORE FUNCTIONS --------------------
	
	KeyboardHero(final boolean isFull) {
		super(APP_TITLE);
		setIconImage(ICON);
		
		setBackground(Graphs.BG_COLOR);
		setFocusable(true);
		setUndecorated(isFull);
		setMinimumSize(new Dimension(320, 200));
		
		addWindowListener(new WindowAdapter() {
			public void windowClosing(final WindowEvent e) {
				KeyboardHero.exitGame();
			} // end method			    
			
			public void windowDeactivated(final WindowEvent e) {
				if(Game.isAutoPause() && Util.getDebugLevel() < 512) {
					Game.pause();
				} // end if
			} // end method
			
			public void windowLostFocus(final WindowEvent e) {
				windowDeactivated(e);
			} // end method
		} // end class
		);
		
		addListeners(this);
		
		if(!isFull) {
			setJMenuBar(menuBar);
			setBounds(Util.getPropInt("lastCooX"), Util.getPropInt("lastCooY"), Util.getPropInt("lastWidth"), Util.getPropInt("lastHeight"));
			setExtendedState(Util.getPropInt("lastState"));
		}
		
		if(fullscreen == isFull) {
			mainFrame = this;
			getContentPane().add(mainPanel);
			setVisible(true);
			requestFocus();
		} else {
			setVisible(false);
		}
	}
	
	private static void createFrames() {
		createMenu();
		createPanels();
		
		normalFrame = new KeyboardHero(false);
		fullFrame = new KeyboardHero(true);
	}
	
	private static void createPanels() {
		mainPanel = new JPanel();
		mainPanel.setLayout(new BorderLayout());
		
		mainPanel.add(subMainPanel = new JPanel(), BorderLayout.CENTER);
		subMainPanel.addComponentListener(new ComponentAdapter() {
			public void componentResized(final ComponentEvent e) {
				final Component component = e.getComponent();
				updateBorderPanels(component.getWidth(), component.getHeight());
			}
		});
		subMainPanel.setBackground(Graphs.BG_COLOR);
		subMainPanel.setLayout(new BorderLayout());
		
		subMainPanel.add(Graphs.initialize(Util.getPropInt("renderer")), BorderLayout.CENTER);
		
		subMainPanel.add(panelNorth = new JPanel(), BorderLayout.NORTH);
		panelNorth.setBackground(Graphs.getColor("sidePanelBg"));
		subMainPanel.add(panelSouth = new JPanel(), BorderLayout.SOUTH);
		panelSouth.setBackground(Graphs.getColor("sidePanelBg"));
		subMainPanel.add(panelWest = new JPanel(), BorderLayout.WEST);
		panelWest.setBackground(Graphs.getColor("sidePanelBg"));
		subMainPanel.add(panelEast = new JPanel(), BorderLayout.EAST);
		panelEast.setBackground(Graphs.getColor("sidePanelBg"));
		
		mainPanel.add(statusPanel = new JPanel(), BorderLayout.SOUTH);
		statusPanel.setBorder(new CompoundBorder(new MatteBorder(1, 0, 0, 0, Color.GRAY), new EmptyBorder(1, 1, 0, 0)));
		statusPanel.setPreferredSize(new Dimension(0, 21));
		statusPanel.setLayout(new BorderLayout());
		statusPanel.add(status);
	}
	
	private static void createMenu() {
		menuBar = new JMenuBar();
		String cache;
		JMenu menu;
		JMenuItem menuitem;
		
		menuBar.add(menu = new JMenu(Util.getMsgMnemonic("Menu_Game")));
		menu.setName("Menu_Game");
		menu.getPopupMenu().setLightWeightPopupEnabled(false);
		menu.setMnemonic(Util.getLastMnemonic());
		menu.setDisplayedMnemonicIndex(Util.getLastMnemonicIndex());
		menu.addMouseListener(new MouseAdapter() {
			public void mousePressed(final MouseEvent e) {
				if(Game.isAutoPause()) Game.pause();
			} // end method
		} // end class
		);
		
		menu.add(menuitem = new JMenuItem(Util.getMsgMnemonic("Menu_NewGame"), Util.getLastMnemonic()));
		menuitem.setDisplayedMnemonicIndex(Util.getLastMnemonicIndex());
		menuitem.setName("Menu_NewGame");
		menuitem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, ActionEvent.CTRL_MASK));
		menuitem.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent e) {
				dialogs.get("newGame").open();
			} // end method
		} // end class
		);
		
		menu.add(menuitem = new JMenuItem(Util.getMsgMnemonic("NewRandomGame"), Util.getLastMnemonic()));
		menuitem.setDisplayedMnemonicIndex(Util.getLastMnemonicIndex());
		menuitem.setName("NewRandomGame");
		menuitem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, ActionEvent.CTRL_MASK));
		menuitem.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent e) {
				Game.newRandomGame();
			} // end method
		} // end class
		);
		
		menu.add(menuitem = new JMenuItem());
		Util.updateButtonText(menuitem, "NewGameFromFile");
		menuitem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F, ActionEvent.CTRL_MASK));
		menuitem.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent e) {
				Game.newGameFromFile();
			} // end method
		} // end class
		);
		
		menu.addSeparator();
		
		menu.add(menuitem = new JMenuItem(Util.getMsgMnemonic("Menu_Pause"), Util.getLastMnemonic()));
		menuitem.setDisplayedMnemonicIndex(Util.getLastMnemonicIndex());
		menuitem.setName("Menu_Pause");
		menuitem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, ActionEvent.ALT_MASK));
		menuitem.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent e) {
				Game.togglePause();
			} // end method
		} // end class
		);
		mPause = menuitem;
		
		menu.add(menuitem = new JMenuItem());
		Util.updateButtonText(menuitem, "ConnectToClient");
		menuitem.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent e) {
				final String response = KeyboardHero.input(Util.getMsg("AddressOfClient"), Util.getProp("lastClient"));
				if(response != null) {
					Util.setProp("lastClient", response);
					final String[] parts = response.split(":", 2);
					int port = 32321;
					if(parts.length == 2) {
						try {
							port = Integer.parseInt(parts[1]);
						} catch(NumberFormatException ex) {
							if(Util.getDebugLevel() > 90) {
								ex.printStackTrace();
							}
						}
					}
					Connection.connectClient(new InetSocketAddress(parts[0], port));
				}
			} // end method
		} // end class
		);
		
		menu.add(menuitem = new JMenuItem(Util.getMsgMnemonic("Menu_GamePref"), Util.getLastMnemonic()));
		menuitem.setDisplayedMnemonicIndex(Util.getLastMnemonicIndex());
		menuitem.setName("Menu_GamePref");
		menuitem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, ActionEvent.CTRL_MASK));
		menuitem.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent e) {
				dialogs.get("settings").open();
			} // end method
		} // end class
		);
		
		menu.addSeparator();
		
		menu.add(menuitem = new JMenuItem(Util.getMsgMnemonic("Menu_LocalToplist"), Util.getLastMnemonic()));
		menuitem.setDisplayedMnemonicIndex(Util.getLastMnemonicIndex());
		menuitem.setName("Menu_LocalToplist");
		menuitem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T, ActionEvent.CTRL_MASK));
		menuitem.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent e) {
				Connection.showLocalToplist();
			} // end method
		} // end class
		);
		
		menu.addSeparator();
		
		menu.add(menuitem = new JMenuItem(Util.getMsgMnemonic("Menu_OnlineToplist"), Util.getLastMnemonic()));
		menuitem.setDisplayedMnemonicIndex(Util.getLastMnemonicIndex());
		menuitem.setName("Menu_OnlineToplist");
		menuitem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, ActionEvent.CTRL_MASK));
		menuitem.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent e) {
				Connection.showOnlineToplist();
			} // end method
		} // end class
		);
		
		menu.add(menuitem = new JMenuItem(Util.getMsgMnemonic("Menu_OpenBrowser"), Util.getLastMnemonic()));
		menuitem.setDisplayedMnemonicIndex(Util.getLastMnemonicIndex());
		menuitem.setName("Menu_OpenBrowser");
		//m_OpenBrowser.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_B, ActionEvent.CTRL_MASK));
		menuitem.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent e) {
				Connection.showBrowser();
			} // end method
		} // end class
		);
		
		/* // It has been put into Settings... 
		menu.add(menuitem = new JCheckBoxMenuItem(Util.getMsgMnemonic("Menu_UpdOnlToplist"), Util.getProp("connToplist").equals("yes")));
		menuitem.setName("Menu_UpdOnlToplist");
		menuitem.setMnemonic(Util.getLastMnemonic());
		menuitem.setDisplayedMnemonicIndex(Util.getLastMnemonicIndex());
		menuitem.addItemListener(new ItemListener() {
			public void itemStateChanged(final ItemEvent e) {
				Util.setProp("connToplist", e.getStateChange() == ItemEvent.SELECTED);
			} // end method
		} // end class
		);
		*/

		menu.addSeparator();
		
		menu.add(menuitem = new JMenuItem(Util.getMsgMnemonic("Menu_Exit"), Util.getLastMnemonic()));
		menuitem.setDisplayedMnemonicIndex(Util.getLastMnemonicIndex());
		menuitem.setName("Menu_Exit");
		menuitem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, ActionEvent.ALT_MASK));
		menuitem.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent e) {
				KeyboardHero.exitGame();
			} // end method
		} // end class
		);
		
		menuBar.add(m_Language = new JMenu(Util.getMsgMnemonic("Menu_Language")));
		m_Language.setName("Menu_Language");
		m_Language.setMnemonic(Util.getLastMnemonic());
		m_Language.setDisplayedMnemonicIndex(Util.getLastMnemonicIndex());
		m_Language.getPopupMenu().setLightWeightPopupEnabled(false);
		m_Language.addMouseListener(new MouseAdapter() {
			public void mousePressed(final MouseEvent e) {
				if(Game.isAutoPause()) Game.pause();
			} // end method
		} // end class
		);
		
		m_Language.add(menuitem = new JMenuItem(Util.getMsgMnemonic("Default"), Util.getLastMnemonic()));
		menuitem.setDisplayedMnemonicIndex(Util.getLastMnemonicIndex());
		menuitem.setName("Default");
		menuitem.setActionCommand("");
		
		menuitem.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent e) {
				final String cache;
				Util.setLang(cache = e.getActionCommand());
				Util.selectMenuItems(m_Language, cache);
			} // end method
		} // end class
		);
		
		m_Language.addSeparator();
		
		for(final String element : Util.getLangs()) {
			m_Language.add(menuitem = new JMenuItem(element, element.charAt(0)));
			menuitem.setActionCommand(element);
			
			menuitem.addActionListener(new ActionListener() {
				public void actionPerformed(final ActionEvent e) {
					final String cache;
					Util.setLang(cache = e.getActionCommand());
					Util.selectMenuItems(m_Language, cache);
				} // end method
			} // end class
			);
		} // end for
		
		menuBar.add(menu = new JMenu(Util.getMsgMnemonic("Menu_View")));
		menu.setName("Menu_View");
		menu.getPopupMenu().setLightWeightPopupEnabled(false);
		menu.setMnemonic(Util.getLastMnemonic());
		menu.setDisplayedMnemonicIndex(Util.getLastMnemonicIndex());
		menu.addMouseListener(new MouseAdapter() {
			public void mousePressed(final MouseEvent e) {
				if(Game.isAutoPause()) Game.pause();
			} // end method
		} // end class
		);
		
		menu.add(menuitem = new JMenuItem(Util.getMsgMnemonic("Menu_FullScreen"), Util.getLastMnemonic()));
		menuitem.setDisplayedMnemonicIndex(Util.getLastMnemonicIndex());
		menuitem.setName("Menu_FullScreen");
		menuitem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F11, 0));
		menuitem.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent e) {
				fullScreenMode(!fullscreen);
			} // end method
		} // end class
		);
		
		menu.addSeparator();
		
		menu.add(m_LaF = new JMenu(Util.getMsgMnemonic("Menu_LaF")));
		m_LaF.setName("Menu_LaF");
		m_LaF.getPopupMenu().setLightWeightPopupEnabled(false);
		m_LaF.setMnemonic(Util.getLastMnemonic());
		m_LaF.setDisplayedMnemonicIndex(Util.getLastMnemonicIndex());
		m_LaF.addMouseListener(new MouseAdapter() {
			public void mousePressed(final MouseEvent e) {
				if(Game.isAutoPause()) Game.pause();
			} // end method
		} // end class
		);
		
		m_LaF.add(menuitem = new JMenuItem(Util.getMsgMnemonic("Default"), Util.getLastMnemonic()));
		menuitem.setDisplayedMnemonicIndex(Util.getLastMnemonicIndex());
		menuitem.setName("Default");
		menuitem.setActionCommand("");
		
		menuitem.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent e) {
				String cache;
				Util.setProp("laf", cache = e.getActionCommand());
				Util.selectMenuItems(m_LaF, cache);
				resetLaF(true);
			} // end method
		} // end class
		);
		
		menu.add(m_Theme = new JMenu(Util.getMsgMnemonic("Menu_Theme")));
		m_Theme.setName("Menu_Theme");
		m_Theme.setMnemonic(Util.getLastMnemonic());
		m_Theme.setDisplayedMnemonicIndex(Util.getLastMnemonicIndex());
		m_Theme.getPopupMenu().setLightWeightPopupEnabled(false);
		m_Theme.addMouseListener(new MouseAdapter() {
			public void mousePressed(final MouseEvent e) {
				if(Game.isAutoPause()) Game.pause();
			} // end method
		} // end class
		);
		
		m_Theme.add(menuitem = new JMenuItem(Util.getMsgMnemonic("Default"), Util.getLastMnemonic()));
		menuitem.setDisplayedMnemonicIndex(Util.getLastMnemonicIndex());
		menuitem.setName("Default");
		menuitem.setActionCommand("");
		
		menuitem.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent e) {
				final String cache;
				Util.setTheme(cache = e.getActionCommand());
				Util.selectMenuItems(m_Theme, cache);
			} // end method
		} // end class
		);
		
		m_Theme.addSeparator();
		
		for(final String element : Util.getThemes()) {
			m_Theme.add(menuitem = new JMenuItem(element, element.charAt(0)));
			menuitem.setActionCommand(element);
			
			menuitem.addActionListener(new ActionListener() {
				public void actionPerformed(final ActionEvent e) {
					final String cache;
					Util.setTheme(cache = e.getActionCommand());
					Util.selectMenuItems(m_Theme, cache);
				} // end method
			} // end class
			);
		} // end for
		
		m_LaF.addSeparator();
		
		for(final UIManager.LookAndFeelInfo element : UIManager.getInstalledLookAndFeels()) {
			m_LaF.add(menuitem = new JMenuItem(cache = element.getName(), cache.charAt(0)));
			menuitem.setActionCommand(element.getClassName());
			
			menuitem.addActionListener(new ActionListener() {
				public void actionPerformed(final ActionEvent e) {
					String cache;
					Util.setProp("laf", cache = e.getActionCommand());
					Util.selectMenuItems(m_LaF, cache);
					resetLaF(true);
				} // end method
			} // end class
			);
		} // end for
		
		if(Graphs.RENDERERS.length > 1) {
			menu.add(m_Renderer = new JMenu(Util.getMsgMnemonic("Menu_Renderer")));
			m_Renderer.setName("Menu_Renderer");
			m_Renderer.getPopupMenu().setLightWeightPopupEnabled(false);
			m_Renderer.setMnemonic(Util.getLastMnemonic());
			m_Renderer.setDisplayedMnemonicIndex(Util.getLastMnemonicIndex());
			m_Renderer.addMouseListener(new MouseAdapter() {
				public void mousePressed(final MouseEvent e) {
					if(Game.isAutoPause()) Game.pause();
				} // end method
			} // end class
			);
			
			for(int i = 0; i < Graphs.RENDERERS.length; ++i) {
				m_Renderer.add(menuitem = new JMenuItem(Graphs.RENDERERS[i], Graphs.RENDERERS[i].charAt(0)));
				menuitem.setActionCommand(Integer.toString(i));
				
				menuitem.addActionListener(new ActionListener() {
					public void actionPerformed(final ActionEvent e) {
						String cache;
						Util.setProp("renderer", cache = e.getActionCommand());
						Util.selectMenuItems(m_Renderer, cache);
						subMainPanel.remove(Graphs.getCanvas());
						subMainPanel.add(Graphs.initialize(Util.getPropInt("renderer")), BorderLayout.CENTER);
						subMainPanel.doLayout();
					} // end method
				} // end class
				);
			} // end for
		}
		
		menu.addSeparator();
		
		menu.add(menuitem = new JMenuItem(Util.getMsgMnemonic("Menu_SongList"), Util.getLastMnemonic()));
		menuitem.setDisplayedMnemonicIndex(Util.getLastMnemonicIndex());
		menuitem.setName("Menu_SongList");
		menuitem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, ActionEvent.CTRL_MASK));
		menuitem.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent e) {
				dialogs.get("songList").open();
			} // end method
		} // end class
		);
		
		menu.addSeparator();
		
		menu.add(menuitem = new JMenuItem(Util.getMsgMnemonic("Menu_ViewProp"), Util.getLastMnemonic()));
		menuitem.setDisplayedMnemonicIndex(Util.getLastMnemonicIndex());
		menuitem.setName("Menu_ViewProp");
		//m_ViewProp.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, ActionEvent.CTRL_MASK));
		menuitem.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent e) {
				((DialogSettings)dialogs.get("settings")).open(DialogSettings.VIEW);
			} // end method
		} // end class
		);
		
		menuBar.add(menu = new JMenu(Util.getMsgMnemonic("Menu_Help")));
		menu.setName("Menu_Help");
		menu.getPopupMenu().setLightWeightPopupEnabled(false);
		menu.setMnemonic(Util.getLastMnemonic());
		menu.setDisplayedMnemonicIndex(Util.getLastMnemonicIndex());
		menu.addMouseListener(new MouseAdapter() {
			public void mousePressed(final MouseEvent e) {
				if(Game.isAutoPause()) Game.pause();
			} // end method
		} // end class
		);
		
		menu.add(menuitem = new JMenuItem(Util.getMsgMnemonic("Menu_Rules"), Util.getLastMnemonic()));
		menuitem.setDisplayedMnemonicIndex(Util.getLastMnemonicIndex());
		menuitem.setName("Menu_Rules");
		menuitem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0));
		menuitem.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent e) {
				dialogs.get("rules").open();
				//JOptionPane.showMessageDialog(mainFrame, new DialogRules(), ((JMenuItem)e.getSource()).getText(), JOptionPane.PLAIN_MESSAGE);
			} // end method
		} // end class
		);
		
		menu.addSeparator();
		
		menu.add(menuitem = new JMenuItem(Util.getMsgMnemonic("Menu_CheckUpdates"), Util.getLastMnemonic()));
		menuitem.setDisplayedMnemonicIndex(Util.getLastMnemonicIndex());
		menuitem.setName("Menu_CheckUpdates");
		menuitem.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent e) {
				Connection.checkUpdates(true);
			} // end method
		} // end class
		);
		
		menu.addSeparator();
		
		menu.add(menuitem = new JMenuItem(Util.getMsgMnemonic("Menu_About"), Util.getLastMnemonic()));
		menuitem.setDisplayedMnemonicIndex(Util.getLastMnemonicIndex());
		menuitem.setName("Menu_About");
		menuitem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0));
		menuitem.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent e) {
				dialogs.get("about").open();
			} // end method
		} // end class
		);
	} // end method
	
	/**
	 * This method adds actually the key adapter to the components. This method works recursively.
	 * 
	 * @param c
	 *            the component to which the key adapter should be applied.
	 * @see #KEY_ADAPTER
	 * @see #CONTAINER_ADAPTER
	 */
	private static void addListeners(Component c) {
		c.addKeyListener(KEY_ADAPTER);
		
		if(c instanceof Container) {
			Container container = (Container)c;
			container.addContainerListener(CONTAINER_ADAPTER);
			for(Component child : container.getComponents()) {
				addListeners(child);
			} // end for
		} // end if
	} // end method
	
	//-------------------- ACTION FUNCTIONS --------------------
	
	static void toogleFullScreenMode() {
		fullScreenMode(!fullscreen);
	}
	
	private static synchronized void fullScreenMode(final boolean isFullScreen) {
		if(fullscreenChangeAllowed) (new Thread() {
			public void run() {
				Graphs.setFullscreenChangeAllowed(fullscreenChangeAllowed = false);
				
				if(isFullScreen) {
					fullscreen = true;
					Graphs.fullScreenStateChanged(fullscreen);
					normalFrame.setVisible(false);
					mainFrame = fullFrame;
					mainFrame.getContentPane().add(mainPanel);
					
					final String device = Util.getProp("viewDevice");
					GraphicsDevice graphicsDevice = null;
					GraphicsEnvironment genv = GraphicsEnvironment.getLocalGraphicsEnvironment();
					if(device.equals("")) {
						graphicsDevice = genv.getDefaultScreenDevice();
					} else {
						for(GraphicsDevice g : genv.getScreenDevices()) {
							if(device.equals(g.getIDstring())) {
								graphicsDevice = g;
								break;
							}
						}
					}
					if(graphicsDevice == null) {
						Util.warning(Util.getMsg("Wrn_NoGraphDevice"));
						Util.setProp("viewDevice", "");
						graphicsDevice = genv.getDefaultScreenDevice();
					}
					
					mainFrame.dispose();
					graphicsDevice.setFullScreenWindow(mainFrame);
					
					String[] parts = Util.getProp("viewResolution").split("x");
					int width, height, colorDepth, refreshRate;
					DisplayMode def = graphicsDevice.getDisplayMode();
					try {
						width = (int)Double.parseDouble(parts[0]);
						height = (int)Double.parseDouble(parts[1]);
					} catch(Exception e) {
						width = def.getWidth();
						height = def.getHeight();
					}
					
					try {
						colorDepth = Util.getPropIntSafe("viewColorDepth");
					} catch(Exception e) {
						colorDepth = def.getBitDepth();
					}
					
					try {
						refreshRate = Util.getPropIntSafe("viewRefreshRate");
					} catch(Exception e) {
						refreshRate = DisplayMode.REFRESH_RATE_UNKNOWN;
					}
					
					try {
						graphicsDevice.setDisplayMode(new DisplayMode(width, height, colorDepth, refreshRate));
					} catch(Exception e) {
						Util.warning(Util.getMsg("Wrn_NotSupDisplayMode"));
						Util.setProp("viewResolution", "");
						Util.setProp("viewColorDepth", "");
						Util.setProp("viewRefreshRate", "");
					}
					
					mainFrame.requestFocus();
				} else {
					fullscreen = false;
					Graphs.fullScreenStateChanged(fullscreen);
					
					for(GraphicsDevice g : GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()) {
						g.setFullScreenWindow(null);
					}
					
					fullFrame.setVisible(false);
					mainFrame = normalFrame;
					mainFrame.getContentPane().add(mainPanel);
					try {
						mainFrame.dispose();
						mainFrame.setVisible(true);
					} catch(Exception e) {
						mainFrame.dispose();
						mainFrame.setVisible(true);
					}
					mainFrame.requestFocus();
					SwingUtilities.invokeLater(new Thread() {
						public void run() {
							updateBorderPanels(subMainPanel.getWidth(), subMainPanel.getHeight());
						}
					});
				}
				
				try {
					Thread.sleep(1000);
				} catch(Exception e) {}
				
				Graphs.setFullscreenChangeAllowed(fullscreenChangeAllowed = true);
			}
		}).start();
	}
	
	/** Exits from the game and closes the application. */
	static void exitGame() {
		try {
			Game.exit();
			closure();
			inApp = false;
		} // end try
		catch(Exception e) {
			System.err.println(e.toString());
		} // end catch
		System.exit(0);
	} // end method
	
	static final void addStatus(Status status) {
		statuses.add(status);
		checkStatuses();
	}
	
	static final void removeStatus(Status status) {
		statuses.remove(status);
		checkStatuses();
	}
	
	private static void checkStatuses() {
		Status status = statuses.peek();
		if(status != lastStatus) {
			setStatus(status);
		}
	}
	
	private static final void setStatus(Status status) {
		lastStatus = status;
		if(status == null) {
			KeyboardHero.status.setText(null);
			KeyboardHero.status.setToolTipText(null);
			KeyboardHero.status.setIcon(null);
			return;
		}
		
		if(status.msgKey != null) {
			setStatusText(status);
		} else {
			KeyboardHero.status.setText(null);
			KeyboardHero.status.setToolTipText(null);
		}
		
		if(status.icon == Status.PLAIN) {
			KeyboardHero.status.setIcon(null);
		} else {
			KeyboardHero.status.setIcon(statusIcons[status.icon]);
		}
	}
	
	private static final void setStatusText(Status status) {
		final String msg = Util.getMsg(status.msgKey);
		final String exception = status.getException();
		
		if(msg != null && !msg.equals("") && exception != null && !exception.equals("")) {
			final String text = Util.withExclamationMark(Util.withAdditionalCharacter(msg, (Character.isLowerCase(exception.charAt(0)) ? ';' : '!')) + ' ' + exception);
			KeyboardHero.status.setText(text);
			KeyboardHero.status.setToolTipText(text);
		} // end if
		else {
			final String text = Util.withExclamationMark((msg != null ? msg : "") + (exception != null ? exception : ""));
			KeyboardHero.status.setText(text);
			KeyboardHero.status.setToolTipText(text);
		} // end else
	}
	
	//-------------------- ALERTING FUNCTIONS --------------------
	
	/**
	 * Alerts an information message.
	 * 
	 * @param msg
	 *            the message to be alerted.
	 */
	static final void alert(final String msg) { //$SUP-MNU$
		alert(msg, APP_TITLE, JOptionPane.INFORMATION_MESSAGE);
	} // end method
	
	/**
	 * Alerts an information message with the given title.
	 * 
	 * @param msg
	 *            the message to be alerted.
	 * @param title
	 *            the title of the message.
	 */
	static final void alert(final String msg, final String title) { //$SUP-MNU$
		alert(msg, title, JOptionPane.INFORMATION_MESSAGE);
	} // end method
	
	/**
	 * Alerts a message with the given type and title.
	 * 
	 * @param msg
	 *            the message to be alerted.
	 * @param title
	 *            the title of the message.
	 * @param type
	 *            the type of the messasge.
	 * @see javax.swing.JOptionPane
	 */
	static final void alert(final String msg, final String title, final int type) {
		if(fullscreen) {
			fullscreenChangeAllowed = true;
			fullScreenMode(false);
		}
		JOptionPane.showMessageDialog(mainFrame, msg, title, type);
	} // end method
	
	/**
	 * Shows a confirmation dialog.
	 * 
	 * @param msg
	 *            the message or question to be displayed.
	 * @return whether the user clicked on the Yes (true) or on the No button (false).
	 */
	static final boolean confirm(final String msg) { //$SUP-MCP$
		return confirm(msg, APP_TITLE);
	} // end method
	
	/**
	 * Shows a confirmation dialog with the given title.
	 * 
	 * @param msg
	 *            the message or question to be displayed.
	 * @param title
	 *            the title of the dialog.
	 * @return whether the user clicked on the Yes (true) or on the No button (false).
	 * @see javax.swing.JOptionPane
	 */
	static final boolean confirm(final String msg, final String title) { //$SUP-MCP$
		if(fullscreen) {
			fullscreenChangeAllowed = true;
			fullScreenMode(false);
		}
		return (JOptionPane.showConfirmDialog(mainFrame, msg, title, JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION);
	} // end method
	
	/**
	 * Shows an input dialog.
	 * 
	 * @param question
	 *            the message or question to be displayed.
	 * @param defValue
	 *            the default answer for the question.
	 * @return the String that the user typed or null if the dialog has been closed.
	 * @see javax.swing.JOptionPane
	 */
	static final String input(final String question, final String defValue) {
		if(fullscreen) {
			fullscreenChangeAllowed = true;
			fullScreenMode(false);
		}
		return JOptionPane.showInputDialog(mainFrame, question, defValue);
	} // end method
	
	static final File midiFile() {
		if(fullscreen) {
			fullscreenChangeAllowed = true;
			fullScreenMode(false);
		}
		final JFileChooser chooser = new JFileChooser(Util.getProp("midiFilePath"));
		chooser.setFileFilter(new FileNameExtensionFilter(Util.getMsg("MidiFiles"), "mid", "midi"));
		int returnVal = chooser.showOpenDialog(mainFrame);
		if(returnVal == JFileChooser.APPROVE_OPTION) {
			final File file =  chooser.getSelectedFile();
			Util.setProp("midiFilePath", file.getPath());
			return file;
		}
		return null;
	}
	
	//-------------------- SETTER / GETTER FUNCTIONS --------------------
	
	final static TreeMap<String, AbstractDialog> getDialogs() {
		return dialogs;
	}
	
	/**
	 * This method stores the pass-key generated by the {@link Util#keygeneration()} method. It is
	 * required in this way, thus the Util will only provide the generated key to the KeyboardHero
	 * class.
	 * 
	 * @param passkey
	 *            the generated pass-key.
	 * @see KeyboardHero#lastPasskey
	 */
	static void setPasskey(String passkey) {
		lastPasskey = passkey;
	} // end method
	
	static final void setLoadingString(String str) {
		loadingString = str;
		if(mainFrame != null) mainFrame.loading(mainFrame.getGraphics());
	}
	
	static final boolean isStartup() {
		return startup;
	}
	
	// -------------------- UTILITY FUNCTIONS --------------------
	
	public void paint(Graphics g) {
		if(startup) {
			loading(g);
		} else {
			super.paint(g);
		}
	}
	
	public void paintComponents(Graphics g) {
		if(startup) {
			loading(g);
		} else {
			super.paintComponents(g);
		}
	}
	
	private void loading(Graphics g) {
		double ratio = getWidth() / (double)480;
		final Font font = new Font("Arial", Font.BOLD, (int)(24 * ratio));
		g.setFont(font);
		g.setColor(Color.BLACK);
		Rectangle2D bounds = font.getStringBounds(loadingString, ((Graphics2D)g).getFontRenderContext());
		g.drawString(loadingString, ((getWidth() - (int)bounds.getWidth()) / 2), ((getHeight() - (int)bounds.getHeight()) / 2));
	}
	
	/**
	 * Prints a byte array created from an image file. Used only to pre-create icons.
	 * 
	 * @param fileName
	 *            the path to the file to be analysed.
	 * @return the generated source code for the data array.
	 */
	@SuppressWarnings("unused")
	private static String getIconData(final String fileName) {
		StringBuffer buff = new StringBuffer();
		try {
			DataInputStream datainputstream = new DataInputStream(new FileInputStream(fileName));
			
			byte[] icondata = new byte[2048];
			final int length = datainputstream.read(icondata);
			buff.append("private static final byte[] ICONDATA = {");
			for(int i = 0; i < length; i++) {
				if(i != 0) buff.append(", ");
				buff.append(icondata[i]);
			} // end for
			buff.append("}; // end initialization");
		} // end try 
		catch(Exception e) {
			e.printStackTrace();
		} // end catch
		return buff.toString();
	} // end method
	
	//-------------------- EVENT FUNCTIONS --------------------
	
	static void updateBorderPanels(final int width, final int height) {
		Dimension cache;
		if(((double)width / height) >= Graphs.WHRATIO) {
			final int fullsize = (int)(width - (height * Graphs.WHRATIO));
			
			panelNorth.setPreferredSize(cache = new Dimension(0, 0));
			panelSouth.setPreferredSize(cache);
			panelWest.setPreferredSize(new Dimension(fullsize / 2, 0));
			panelEast.setPreferredSize(new Dimension(fullsize - fullsize / 2, 0));
		} else {
			final int fullsize = (int)(height - (width / Graphs.WHRATIO));
			panelNorth.setPreferredSize(new Dimension(0, fullsize / 2));
			panelSouth.setPreferredSize(new Dimension(0, fullsize - fullsize / 2));
			panelWest.setPreferredSize(cache = new Dimension(0, 0));
			panelEast.setPreferredSize(cache);
		}
		
		subMainPanel.doLayout();
	}
	
	static void updateTexts() {
		Util.updateRecursiveText(mainFrame);
		
		if(lastStatus != null && lastStatus.msgKey != null) {
			setStatusText(lastStatus);
		}
		
		Graphs.updateTexts();
		
		for(AbstractDialog dialog : dialogs.values()) {
			dialog.updateTexts();
		}
	}
	
	static void updatePauseText(final String key) {
		Util.updateButtonText(mPause, key);
	}
	
	/**
	 * Resets the Look and Feel based on the appropriate settings.
	 * 
	 * @param fromMenu
	 *            determines whether this method called by the user from the menu or automatically
	 *            when some other appropriate event occurs.
	 */
	private static void resetLaF(final boolean fromMenu) {
		String className = Util.getProp("laf");
		
		if(className.length() == 0) {
			className = Util.DEF_LAF;
		}
		
		if(className == UIManager.getLookAndFeel().getClass().getName()) {
			resetIcons();
			return;
		}
		
		try {
			UIManager.setLookAndFeel(className);
			if(fromMenu) {
				//mainFrame.setVisible(false);
				//mainFrame.createMenu();
				//mainFrame.setVisible(true);
				//Util.updateRecursiveUI(menuBar);
				Util.updateRecursiveUI(mainFrame);
				
				for(AbstractDialog dialog : dialogs.values()) {
					dialog.updateUI();
				}
			} // end if
		} // end try
		catch(final Exception e) {
			Util.setProp("laf", "");
			Util.error("Error while setting up the look and feel", e.toString());
		} // end catch
		
		resetIcons();
		
		//DialogSettings.getInstance().setUndecorated(true);
		//DialogSettings.getInstance().getRootPane().setWindowDecorationStyle(JRootPane.PLAIN_DIALOG);
		
	} // end method
	
	private static void resetIcons() {
		for(int i = 0; i < Status.ICON_TEXTS.length; ++i) {
			statusIcons[i] = new ScaledIcon(UIManager.getIcon(Status.ICON_TEXTS[i]), 18, 18, Image.SCALE_DEFAULT);
		}
		
		if(lastStatus != null && lastStatus.icon != Status.PLAIN) {
			status.setIcon(statusIcons[lastStatus.icon]);
		}
	}
	
	static void resetTitle(String title) {
		title = (title == null || title.equals("") ? APP_TITLE : APP_TITLE + " - " + title);
		normalFrame.setTitle(title);
		fullFrame.setTitle(title);
		fullFrame.setTitle(title);
	}
	
	/**
	 * This method is called when the application is closing. It saves the properties and closes the
	 * possibly open sound streams.
	 */
	static void closure() {
		Util.setProp("fullscreen", fullscreen);
		Util.setProp("lastState", normalFrame.getExtendedState() & ~JFrame.ICONIFIED);
		
		normalFrame.setVisible(true);
		normalFrame.setExtendedState(JFrame.NORMAL);
		normalFrame.setVisible(false);
		fullFrame.setVisible(false);
		Util.setProp("lastCooX", normalFrame.getX());
		Util.setProp("lastCooY", normalFrame.getY());
		Util.setProp("lastWidth", normalFrame.getWidth());
		Util.setProp("lastHeight", normalFrame.getHeight());
		
		for(AbstractDialog dialog : dialogs.values()) {
			dialog.closure();
		}
		
		Connection.closure();
		Graphs.closure();
		Game.closure();
		MidiSequencer.closure();
		MidiDevicer.closure();
		Util.closure();
	} // end method
	
	//-------------------- DEBUGGING FUNCTIONS --------------------
	
	/**
	 * This method serves security purposes. Provides an integrity string that will be checked by
	 * the {@link Connection#integrityCheck()} method; thus the application can only be altered if
	 * the source is known. Every class in the {@link keyboardhero} package has an integrity string.
	 * 
	 * @return the string of this class used for integrity checking.
	 */
	static String getIntegrityString() {
		return "7zH*SI;.asdj3as-dm";
	} // end method
	
	static void startDebugMenu() {
		(new Thread() {
			public void run() {
				boolean valid;
				String[] commands;
				if(Util.IS_DEVELOPER_BUILD) {
					valid = true;
					commands = new String[] {"AbstractDialog Debug", "Connection Debug", "DialogAbout Debug", "DialogNewGame Debug", "DialogRules Debug", "DialogSettings Debug", "DialogSongList Debug", "DialogToplist Debug", "Game Debug", "Graphs Debug", "MidiDevicer Debug", "MidiSequencer Debug", "MidiSong Debug", "RendererSwing Debug", "Tester Debug", "Util Debug", "getIconData(fileName)", "Pass-key generation"}; // end initialization
				} // end if
				else {
					valid = false; //$SUP-SU$
					commands = new String[] {"AbstractDialog Debug", "Connection Debug", "DialogAbout Debug", "DialogNewGame Debug", "DialogRules Debug", "DialogSettings Debug", "DialogSongList Debug", "DialogToplist Debug", "Game Debug", "Graphs Debug", "MidiDevicer Debug", "MidiSequencer Debug", "MidiSong Debug", "RendererSwing Debug", "Tester Debug", "Util Debug", "getIconData(fileName)"}; // end initialization
					try {
						int k = 3;
						while(--k >= 0) { // $SUP-SU$
							if(Util.keycheck(Tester.readString("Please enter the pass-key"))) {
								valid = true;
								break;
							} // end if
							System.out.println("Invalid pass-key!");
						} // end while
					} // end try
					catch(Exception e) {
						System.err.println(e.toString());
					} // end catch
				} // end else
				
				if(valid) (new Tester("Debug Menu", commands) {
					void menu(int choice) throws Exception { //$SUP-MNU$
						switch(choice) {
							case 5:
								AbstractDialog.TESTER.start();
								break;
							case 6:
								Connection.TESTER.start();
								break;
							case 7:
								DialogAbout.TESTER.start();
								break;
							case 8:
								DialogNewGame.TESTER.start();
								break;
							case 9:
								DialogRules.TESTER.start();
								break;
							case 10:
								DialogSettings.TESTER.start();
								break;
							case 11:
								DialogSongList.TESTER.start();
								break;
							case 12:
								DialogToplist.TESTER.start();
								break;
							case 13:
								Game.TESTER.start();
								break;
							case 14:
								Graphs.TESTER.start();
								break;
							case 15:
								MidiDevicer.TESTER.start();
								break;
							case 16:
								MidiSequencer.TESTER.start();
								break;
							case 17:
								MidiSong.TESTER.start();
								break;
							case 18:
								RendererSwing.TESTER.start();
								break;
							case 19:
								Tester.TESTER.start();
								break;
							case 20:
								Util.TESTER.start();
								break;
							case 21:
								final String fileName = readString("String fileName");
								System.out.println(getIconData(fileName));
								break;
							case 22:
								Util.keygeneration();
								System.out.println("The generated pass-key: " + lastPasskey);
								break;
							default:
								baseMenu(choice);
								break;
						} // end switch
					} // end method
					
					private void runUnitTestsPart() throws Exception {
						AbstractDialog.TESTER.runUnitTests();
						Connection.TESTER.runUnitTests();
						DialogAbout.TESTER.runUnitTests();
						DialogNewGame.TESTER.runUnitTests();
						DialogRules.TESTER.runUnitTests();
						DialogSettings.TESTER.runUnitTests();
						DialogSongList.TESTER.runUnitTests();
						DialogToplist.TESTER.runUnitTests();
						Game.TESTER.runUnitTests();
						Graphs.TESTER.runUnitTests();
						MidiDevicer.TESTER.runUnitTests();
						MidiSequencer.TESTER.runUnitTests();
						MidiSong.TESTER.runUnitTests();
						RendererSwing.TESTER.runUnitTests();
						Tester.TESTER.runUnitTests();
						Util.TESTER.runUnitTests();
					} // end method
					
					void runUnitTests() throws Exception {
						superTestStart();
						
						runUnitTestsPart();
						
						superTestEnd();
					} // end method
				}).start(2); // end class
			}
		}).start();
	}
	
	//-------------------- APPLICATION STARTER FUNCTIONS --------------------
	
	/**
	 * This method initializes the application and the game and calls the initialization methods of
	 * the other classes. It creates a new game, draws the initial screen and shows the help if it
	 * is the first time when the game is started. This method contains the loop which runs the game
	 * itself.
	 * 
	 * @see Game#doRun()
	 */
	private static void initialize() {
		resetLaF(false);
		JDialog.setDefaultLookAndFeelDecorated(true);
		
		createFrames();
		mainFrame.loading(mainFrame.getGraphics());
		if(fullscreen) fullScreenMode(true);
		
		new DialogRules(normalFrame);
		new DialogNewGame(normalFrame);
		new DialogSettings(normalFrame);
		new DialogAbout(normalFrame);
		new DialogSongList(normalFrame);
		new DialogToplist(normalFrame);
		
		SongSelector.refresh();
		
		Util.selectMenuItems(m_Language, Util.getProp("lang"));
		Util.selectMenuItems(m_LaF, Util.getProp("laf"));
		Util.selectMenuItems(m_Renderer, Util.getProp("renderer"));
		Util.selectMenuItems(m_Theme, Util.getProp("theme"));
		
		if(Util.getPropBool("connClients")) Connection.startListening();
		
		(new Thread() {
			public void run() {
				try {
					MidiDevicer.initialize();
					startup = false;
					mainFrame.repaint();
					
					if(Util.getPropBool("connUpdate")) Connection.checkUpdates(false);
					
					if(Util.getPropBool("firstTimeRun")) {
						(new Thread() {
							public void run() {
								Util.setProp("firstTimeRun", false);
								dialogs.get("rules").open();
							}
						}).start();
					}
					
					while(inApp) {
						Game.doRun();
					} // end while
				} finally {
					System.exit(0); //$SUP-ONF$
				} // end finally
			} // end method
		}).start(); // end class
	} // end method
	
	/**
	 * This method starts the application. This method is called from the main method of the
	 * {@link KeyboardHero} class and other classes if this build is a developers' one.
	 */
	static void startApp() {
		final String libraryPath = System.getProperty("java.library.path");
		System.setProperty("java.library.path", (libraryPath.equals("") ? "lib" : "lib" + File.pathSeparator + libraryPath));
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				initialize();
			} // end method
		}); // end class
	} // end method
	
	/**
	 * Checks whether the required arguments is in the whole arugment list.
	 * 
	 * @param args
	 *            the arguments given to the program.
	 * @param reqs
	 *            the arguments that method should look for.
	 * @return true if at least one of the required arguments is found.
	 */
	private static boolean checkArgs(final String[] args, final String[] reqs) {
		for(int i = 0; i < args.length; i++) {
			for(int j = 0; j < reqs.length; j++) {
				if(args[i].equalsIgnoreCase(reqs[j])) return true;
				if(args[i].equalsIgnoreCase("-" + reqs[j])) return true;
				if(args[i].equalsIgnoreCase("--" + reqs[j])) return true;
				if(args[i].equalsIgnoreCase("/" + reqs[j])) return true;
			} // end for
		} // end for
		return false;
	} // end method
	
	static void handleArgs(final String[] args, boolean fromMain) {
		if(args.length >= 1) {
			if(checkArgs(args, new String[] {"h", "help", "?"} /* end initialization */)) {
				final String msg = "h, help\n\tPrints this help text.\n\nl, license\n\tDisplays the license agreement.\n\nd, debug\n\tStarts the program with the debugging interface.\n";
				System.out.println(APP_TITLE + " Command Line Arguments\n" + msg); //$SUP-EVC$
				JOptionPane.showMessageDialog(null, msg, APP_TITLE + " Command Line Arguments", JOptionPane.INFORMATION_MESSAGE);
			} // end if (help)
			if(checkArgs(args, new String[] {"l", "license", "terms", "conditions", "warranty"} /* end initialization */)) {
				System.out.println(Util.wordWrap(APP_LICENSE, 70)); //$SUP-EVC$
				final JTextArea textArea = new JTextArea(KeyboardHero.APP_LICENSE);
				textArea.setEditable(false);
				textArea.setBackground(new Color(UIManager.getColor("Panel.background").getRGB()));
				textArea.setBorder(new EmptyBorder(10, 10, 10, 10));
				textArea.setFont(new Font(Font.MONOSPACED, 0, 12));
				textArea.setLineWrap(true);
				textArea.setWrapStyleWord(true);
				textArea.setAutoscrolls(true);
				final JScrollPane scrollPane = new JScrollPane(textArea);
				scrollPane.setPreferredSize(new Dimension(515, 375));
				JOptionPane.showMessageDialog(null, scrollPane, APP_TITLE + " License Agreement", JOptionPane.INFORMATION_MESSAGE);
			} // end if (license)
			if(fromMain && checkArgs(args, new String[] {"d", "debug"} /* end initialization */)) {
				SwingUtilities.invokeLater(new Thread() {
					public void run() {
						startDebugMenu();
					}
				});
			} // end if (debug)
		} // end if (args.length)
	}
	
	/**
	 * The main function of the whole package / application. This method will start when the
	 * KeyboardHero game starts, checks the arguments and calls the {@link KeyboardHero#startApp()}
	 * method.
	 * <p>
	 * The possible command line arguments are: h, ?, help - for help of switches; d, debug - for
	 * debug mode.
	 * 
	 * @param args
	 *            the command line arguments.
	 */
	public static void main(final String[] args) {
		startApp();
		handleArgs(args, true);
	} // end method
} // end class
